package blyth;

import java.awt.Point;
import java.util.ArrayList;

/*
 * The information about the tile's of the world is stored
 * in this class.
 */
public class World {

    public final static int worldWidth = 60;
    public final static int worldHeigth = 60;
    public final static int maxEatPerTurn = 20;
    public final static int foodLostPerTurn = 50;
    public final static int lowEnergy = 300;
    public final static int considerIntelAgeValid = 8;
    public final static int maxFoods = 2;
    public final static int foodlessTurnLimit = 30;
    public static int turnsSinceLastFoodAction = -120;
    public static int turn = -1; // The turn we are in, from our perception
    public Tile[][] tile;
    public ArrayList<Agent> Friendlies;
    public ArrayList<Agent> Hostiles;
    public ArrayList<Food> Foods;
    public Astar astar;
    public ArrayList<Point> referencePoints;
    public ArrayList<Point> checkPoints;
    public boolean myTeamIDSet;
    public int myTeamID;
    public int leaderID;
    public int tasksGiven;
    // Defines wich checkPoint should the non-leader agents aim
    public int rotationState;
    public int timeBeforeRotation;
    public int timeSinceRotation;

    public World() {
        tile = new Tile[worldWidth][worldHeigth];
        for (int x = 0; x < worldWidth; x++) {
            for (int y = 0; y < worldHeigth; y++) {
                tile[x][y] = new Tile(CalculateEdgeHassle(x, y));
            }
        }
        Friendlies = new ArrayList<Agent>();
        Hostiles = new ArrayList<Agent>();
        Foods = new ArrayList<Food>();
        astar = new Astar(this);
        referencePoints = new ArrayList<Point>();
        referencePoints.add(new Point(30, 30));
        referencePoints.add(new Point(49, 49));
        referencePoints.add(new Point(30, 49));
        referencePoints.add(new Point(10, 49));
        referencePoints.add(new Point(10, 30));
        referencePoints.add(new Point(10, 10));
        referencePoints.add(new Point(30, 10));
        referencePoints.add(new Point(49, 10));
        referencePoints.add(new Point(49, 30));
        checkPoints = new ArrayList<Point>();
        for (int i = 0; i < referencePoints.size(); i++) {
            checkPoints.add((Point) referencePoints.get(i).clone());
        }
        myTeamIDSet = false;
        myTeamID = -1;
        leaderID = 0;
        tasksGiven = 0;

        rotationState = 0;
        timeSinceRotation = -100;
        timeBeforeRotation = 40;
    }

    public void CalculateCheckpoints() {
        int minHassle = -1, x = 0, y = 0;
        Point p = null;
        for (int i = 0; i < referencePoints.size(); i++) {
            p = referencePoints.get(i);
            for (WorldIterator it = new WorldIterator(this, p.x, p.y, 10, true, 1);
                    it.IsNotEnd(); it.Next()) {
                if (minHassle == -1 || minHassle > it.Get().getStaticHassle()
                        || (minHassle == it.Get().getStaticHassle()
                        && Heur.Manhattan(p.x, p.y, it.x, it.y) < Heur.Manhattan(p.x, p.y, x, y))
                        || (minHassle == it.Get().getStaticHassle()
                        && Heur.Manhattan(p.x, p.y, it.x, it.y) == Heur.Manhattan(p.x, p.y, x, y)
                        && Math.random() > 0.5)) {
                    minHassle = it.Get().getStaticHassle();
                    x = it.x;
                    y = it.y;
                }
            }
            checkPoints.get(i).setLocation(x, y);
        }
    }

    public void MyTeamID(int myTeamID) {
        if (myTeamIDSet == false) {
            this.myTeamID = myTeamID;
            myTeamIDSet = true;
        }
    }

    public void ManageHostileAgent(Agent agent) {
        // Is it really hostile?
        if (agent.team != myTeamID) {
            // Is agent on list?
            int i;
            for (i = 0; i < Hostiles.size() && Hostiles.get(i).id != agent.id; i++);
            if (i == Hostiles.size()) {
                Hostiles.add(agent);
            } else {
                Hostiles.get(i).RefreshIntel(agent);
            }
        }
    }

    public Agent ManageFriendlyAgent(Agent agent) {
        // Is it really friendly?
        if (agent.team == myTeamID) {
            // Is this the first agent?
            if (Friendlies.isEmpty()) {
                Friendlies.add(agent);
                leaderID = agent.id;
                AddInitialTask(agent);
            } else {
                // Is agent on list?
                int i;
                for (i = 0; i < Friendlies.size() && Friendlies.get(i).id != agent.id; i++);
                if (i == Friendlies.size()) {
                    Friendlies.add(agent);
                    AddInitialTask(agent);
                } else {
                    Friendlies.get(i).RefreshIntel(agent);
                    agent = Friendlies.get(i);
                }
            }
            // Manage Tile info
            if (tile[agent.position.x][agent.position.y].isVisited() == false
                    && agent.inWater) {
                // Setting water hassle
                for (WorldIterator it = new WorldIterator(this, agent.position.x, agent.position.y,
                            1, true, 1); it.IsNotEnd(); it.Next()) {
                        tile[it.x][it.y].setWaterHassle(
                                Heur.Manhattan(it.x, it.y, agent.position.x, agent.position.y));
                    }
                // Recalculate checkpoints, based on new hassle values
                CalculateCheckpoints();
                // Setting water probability
                if (Heur.Manhattan(agent.position, agent.lastPosition) == 1) {
                    int dx = agent.position.x - agent.lastPosition.x,
                            dy = agent.position.y - agent.lastPosition.y;
                    if (IsValidTile(agent.position.x + dx, agent.position.y + dy)) {
                        if (tile[agent.lastPosition.x][agent.lastPosition.y].isWater) {
                            tile[agent.position.x + dx][agent.position.y + dy].addWaterProbability(0.5);
                        } else {
                            tile[agent.position.x + dx][agent.position.y + dy].addWaterProbability(1.0);
                        }
                    }
                    if (IsValidTile(agent.position.x + dy, agent.position.y + dx)) {
                        tile[agent.position.x + dy][agent.position.y + dx].addWaterProbability(0.5);
                    }
                    if (IsValidTile(agent.position.x - dy, agent.position.y - dx)) {
                        tile[agent.position.x - dy][agent.position.y - dx].addWaterProbability(0.5);
                    }
                } else {
                    // We were spawned in water, or pushed to water
                    for (WorldIterator it = new WorldIterator(this, agent.position.x, agent.position.y,
                            1, false, 1); it.IsNotEnd(); it.Next()) {
                        tile[it.x][it.y].addWaterProbability(0.3);
                    }
                }
            }
            // Refresh seen tiles
            Point p = new Point();
            for (WorldIterator it = new WorldIterator(this, agent.position.x, agent.position.y, 10,
                    true, 0); it.IsNotEnd(); it.Next()) {
                p.setLocation(it.x, it.y);
                if (agent.CanSee(p)) {
                    // Get enemy on that tile
                    Agent enemy = null;
                    for (int i = 0; i < Hostiles.size(); i++) {
                        if (Hostiles.get(i).position.equals(p)) {
                            enemy = Hostiles.get(i);
                        }
                    }
                    tile[it.x][it.y].Refresh(enemy);
                }
            }
            // Visit this tile
            tile[agent.position.x][agent.position.y].Visit(agent.inWater);
            // If this is the leader, increase turns
            if (agent.id == leaderID) {
                Turn();
            }
            // Movement related stuff
            if (agent.lastPosition.equals(agent.position)) {
                agent.timeSpentHere++;
            } else {
                // We were attacked!
                if (Heur.Manhattan(agent.lastPosition, agent.position) > 1) {
                    agent.GetTask().attacked++;
                }
                agent.timeSpentHere = 0;
                agent.turnsMade = 0;
                agent.waited = 0;
            }
            agent.lastPosition.setLocation(agent.position);
        }
        return agent;
    }

    public void ManageFood(Agent agent, ArrayList<Food> foods) {
        // Refresh, add or remove the foods based on the agent's belief
        Food food = null;
        for (int i = 0; i < Foods.size(); i++) {
            food = Foods.get(i);
            if (agent.CanSee(food.position)) {
                // Does food still exist?
                boolean foodExists = false;
                for (int j = 0; j < foods.size(); j++) {
                    if (food.position.equals(foods.get(j).position)) {
                        foodExists = true;
                        food.RefreshIntel(foods.get(j));
                    }
                }
                if (foodExists == false) {
                    Foods.remove(i);
                    i--;
                }
            }
        }
        // Add all the foods, that are not added yet
        for (int i = 0; i < foods.size(); i++) {
            boolean alreadyAdded = false;
            for (int j = 0; j < Foods.size(); j++) {
                if (Foods.get(j).position.equals(foods.get(i).position)) {
                    alreadyAdded = true;
                }
            }
            if (alreadyAdded == false) {
                Foods.add(foods.get(i));
            }
        }
    }

    public void AddInitialTask(Agent myAgent) {
        ExploreTask exploreTask = new ExploreTask(this, checkPoints.get(tasksGiven));
        exploreTask.assignedTo = myAgent;
        myAgent.tasks.add(exploreTask);
        tasksGiven++;
        if (tasksGiven > 1) {
            tasksGiven++;
        }
    }

    public void ValidateTasks() {
        // Clear invalid tasks from agents
        Agent myAgent = null;
        Task task = null;
        for (int i = 0; i < Friendlies.size(); i++) {
            myAgent = Friendlies.get(i);
            for (int k = 0; k < myAgent.tasks.size(); k++) {
                task = myAgent.tasks.get(k);
                if (task.IsValid() == false) {
                    myAgent.tasks.remove(task);
                    k--;
                }
            }
        }
    }
    
    public void MakeNewTasks() {
        // Search for eat tasks
        EatTask eatTask = null;
        while ((eatTask = EatTask.MakeNew(this)) != null) {
            AssignTask(eatTask);
        }
        // Search for heal tasks
        HealTask healTask = null;
        while ((healTask = HealTask.MakeNew(this)) != null) {
            AssignTask(healTask);
        }
        // Search for check tasks (we need only 1)
        CheckTask checkTask = CheckTask.MakeNew(this);
        if (checkTask != null) {
            AssignTask(checkTask);
        }
    }
    
    public void AssignTask(Task task) {
        Agent idealAgent = null;
        int lowestAvailability = -1, currentAvailability;
        for (int i = 0; i < Friendlies.size(); i++) {
            currentAvailability = task.AvailabilityFrom(Friendlies.get(i));
            if (lowestAvailability == -1 || currentAvailability < lowestAvailability) {
                lowestAvailability = currentAvailability;
                idealAgent = Friendlies.get(i);
            }
        }
        idealAgent.tasks.add(task); 
        task.assignedTo = idealAgent;
    }
       
    public void ManageRotation() {
        // Check for explorer rotation
        if (IsRotationReady()) {
            Agent agent = null;
            int newState;
            rotationState++;
            if (rotationState > 1) {
                rotationState = 0;
            }
            // Start from 1: skip the leader
            for (int i = 1; i < Friendlies.size(); i++) {
                Task exploreTask = null;
                agent = Friendlies.get(i);
                for (int k = 0; k < agent.tasks.size(); k++) {
                    if (agent.tasks.get(k).priority == 1) {
                        exploreTask = agent.tasks.get(k);
                    }
                }
                newState = (i-1)*2 + 1 + rotationState;
                exploreTask.goal = checkPoints.get(newState);
            }
        }
    }   

    public boolean IsTileFree(Point position) {
        boolean isThereWay = false;
        Point p = new Point();
        for (WorldIterator it = new WorldIterator(this, position.x, position.y,
                            1, false, 1); it.IsNotEnd(); it.Next()) {
            p.setLocation(it.x, it.y);
            if (IsThereEnemy(p) == false) {
                isThereWay = true;
            }            
        }
        return isThereWay;
    }
    
    // Get direction required to look, from two points next to each other
    public int ToDir(Point from, Point to) {
        int x = to.x - from.x, y = from.y - to.y;
        if (y == 1) {
            return 0;
        } else if (x == 1) {
            return 1;
        } else if (y == -1) {
            return 2;
        }
        return 3;        
    }
    
    public Command Approach(Agent myAgent, Point goal) {
        // Am I blocked?
        if (IsTileFree(myAgent.position) == false) {
            myAgent.goalBlocked = false;
            myAgent.isBlocked = true;
            // Make the agent wait for a while
            WaitTask task = new WaitTask(this, 10, 30);
            task.assignedTo = myAgent;
            myAgent.tasks.add(task);
            return new Command(Command.Action.Wait, 0, 0);            
        } else if (IsTileFree(goal) == false) {
            // Is Goal blocked?
            myAgent.goalBlocked = true;
            myAgent.isBlocked = false;
            return new Command(Command.Action.Wait, 0, 0);            
        }
        myAgent.goalBlocked = false;
        myAgent.isBlocked = false;
        
        // Astar to build a route, with goal 'n'
        Node n = astar.NextStep(myAgent.position, goal);
        if (n == null) {
            // Routing error
            return new Command(Command.Action.Wait, 0, 0);
        }
        boolean dontTurn = false;

        while (n.reachedFrom.reachedFrom != null) {
            n = n.reachedFrom;
        }
        if (World.turn - tile[n.point.x][n.point.y].lastCheck <= 2) {
            dontTurn = true;
        }
        int desiredDirection = ToDir(myAgent.position, n.point);

        // Checking if turning is accurate for goal
        if (dontTurn == false && myAgent.direction != desiredDirection) {
            return new Command(Command.Action.Turn, desiredDirection, 0);
        }
        // Approaching goal
        return new Command(Command.Action.Step, desiredDirection, 0);
    }

    public Agent IsThereFriendly(Point position) {
        for (int j = 0; j < Friendlies.size(); j++) {
            if (Friendlies.get(j).position.equals(position)) {
                return Friendlies.get(j);
            }
        }
        return null;
    }

    public static int CalculateEdgeHassle(int x, int y) {
        int minEdgeDistance = Math.min(worldWidth - 1 - x,
                worldHeigth - 1 - y);
        minEdgeDistance = Math.min(minEdgeDistance, x);
        minEdgeDistance = Math.min(minEdgeDistance, y);
        if (minEdgeDistance < 10) {
            //return 10 - minEdgeDistance;
            return 5;
        }
        return 0;
    }

    public boolean IsRotationReady() {
        // We already know all the food, or the last rotation was recently
        if (Foods.size() >= 2 || timeSinceRotation < timeBeforeRotation) {
            return false;
        }
        int numberOfReadyAgents = 0;
        Agent agent = null;
        // Start from 1: skip the leader
        for (int i = 1; i < Friendlies.size(); i++) {
            agent = Friendlies.get(i);
            if (agent.turnsMade > 4 && agent.GetTask().priority == 1
                    && Heur.Bird(agent.position, agent.GetTask().goal) <= 1) {
                numberOfReadyAgents++;
            }
        }
        if (numberOfReadyAgents >= UsefulAgents() - 2) {
            timeSinceRotation = 0;
            return true;
        }
        return false;
    }

    public int TeamEnergy() {
        int teamEnergy = 0;
        for (int i = 0; i < Friendlies.size(); i++) {
            teamEnergy += Friendlies.get(i).energy;
        }
        return teamEnergy;
    }

    private int UsefulAgents() {
        int useful = 0;
        for (int i = 0; i < Friendlies.size(); i++) {
            if (Friendlies.get(i).IsUseful()) {
                useful++;
            }
        }
        return useful;
    }

    private void Turn() {
        turn++;
        timeSinceRotation++;
        turnsSinceLastFoodAction++;
    }

    public Point AttackResult(Agent myAgent, Point pos) {
        Point result = (Point) pos.clone();
        Point attack = myAgent.AttackVector();
        result.setLocation(result.x + attack.x, result.y + attack.y);
        while (IsValidTile(result.x, result.y) && IsThereFriendly(result) == null
                && Heur.Manhattan(result, pos) <= 10) {
            result.setLocation(result.x + attack.x, result.y + attack.y);
        }
        result.setLocation(result.x - attack.x, result.y - attack.y);
        return result;
    }

    public boolean IsThereEnemy(Point pos) {
        Tile t = tile[pos.x][pos.y];
        if (t.lastEnemySeenHere == null) {
            return false;
        }
        if (t.lastEnemySeenHere.position.equals(pos)) {
            return World.turn - t.enemyWasHere <= considerIntelAgeValid;
        } else {
            t.lastEnemySeenHere = null;
            return false;
        }
    }

    public Agent GetEnemy(Point pos) {
        if (IsThereEnemy(pos)) {
            return tile[pos.x][pos.y].lastEnemySeenHere;
        }
        return null;
    }

    public static boolean IsValidTile(int x, int y) {
        if (x < 0 || x > worldWidth - 1 || y < 0 || y > worldHeigth - 1) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        String friendInfo = new String();
        for (int i = 0; i < Friendlies.size(); i++) {
            friendInfo += "    ID: " + Friendlies.get(i).id
                    + "      Energy: " + Friendlies.get(i).energy
                    + "\n        TaskPri: " + Friendlies.get(i).GetTask().priority
                    + "  goal: " + Friendlies.get(i).GetTask().goal.x + "," 
                    + Friendlies.get(i).GetTask().goal.y + "\n";
        }        
        return "myTeamIDSet: " + myTeamIDSet + "\n"
                + "leaderID: " + leaderID + "\n"
                + "tasksGiven: " + tasksGiven + "\n"
                + "Friendlies: " + Friendlies.size() + "\n"
                + friendInfo
                + "Hostiles: " + Hostiles.size() + "\n"
                + "Foods: " + Foods.size() + "\n"
                + "\n" + "TeamEnergy: " + TeamEnergy() + "\n"
                + "\n" + "teamTime: " + Knowledge.myTeamTimeLeft + "\n"
                + "turn: " + turn + "\n"
                + "lastEat: " + World.turnsSinceLastFoodAction + "\n";
    }
}
